from abc import ABC
from typing import List, Optional
from abc import ABC, abstractmethod


def add_indent(l: List[str]) -> List[str]:
    return ['    ' + s for s in l]


not_impl_body = ['raise NotImplementedError()']
empty_body = ['']
pass_body = ['pass']

# def to_ts_type(t: str) -> str:
py2ts_types = {
    'None': 'void',
    'str': 'string',
    'int': 'number',
    'float': 'number',
    'bool': 'boolean',
    'List': 'Array',
    'Dict': 'Object',
    'Any': 'any',
}


class Arg:
    def __init__(self, name: str, type: str) -> None:
        self.name = name
        self.type = type


class Function(ABC):
    def __init__(self, name: str, args: List[Arg], return_type: str, body: List[str] = empty_body, is_abstract: bool = False) -> None:
        self.name = name
        self.args = args
        self.return_type = return_type
        self.body = body
        self.is_abstract = is_abstract


class FunctionGenerator(ABC):
    @abstractmethod
    def generate(func: Function) -> List[str]:
        raise NotImplementedError()


class PyFunctionGenerator(FunctionGenerator):
    @staticmethod
    def generate(func: Function) -> List[str]:
        ret = []
        if func.is_abstract:
            ret.append('@abstractmethod')
        arg_list = '(self, ' + ''.join([f'{arg.name}: {arg.type}, ' for arg in func.args]) + ')'
        ret.extend([
            f'def {func.name}{arg_list} -> {func.return_type}:',
            *add_indent(func.body if func.body != empty_body else pass_body),
        ])
        return ret

class TsFunctionGenerator(FunctionGenerator):
    @staticmethod
    def generate(func: Function) -> List[str]:
        arg_list = '(' + ''.join([f'{arg.name}: {py2ts_types[arg.type]}, ' for arg in func.args]) + ')'
        func_def = f'{func.name}{arg_list}: {py2ts_types[func.return_type]}'
        if func.is_abstract:
            return [f'abstract {func_def};']
        else:
            return [
                func_def + ' {',
                *add_indent(func.body),
                '}'
            ]


class Class:
    def __init__(self, name: str, functions: List[Function], is_abstract: bool = False) -> None:
        self.name = name
        self.functions = functions
        self.is_abstract = is_abstract


class ClassGenerator(ABC):
    @abstractmethod
    def generate(cls: Class) -> List[str]:
        raise NotImplementedError()


class PyClassGenerator(ClassGenerator):
    @staticmethod
    def generate(cls: Class) -> List[str]:
        func_codes = []
        for func in cls.functions:
            func_codes.append('')
            func_codes.extend(PyFunctionGenerator.generate(func))
        return [
            f'class {cls.name}{"(ABC)" if cls.is_abstract else ""}:',
            *add_indent(func_codes)
        ]


class TsClassGenerator(ClassGenerator):
    @staticmethod
    def generate(cls: Class) -> List[str]:
        func_codes = []
        for func in cls.functions:
            func_codes.append('')
            func_codes.extend(TsFunctionGenerator.generate(func))
        return [
            f'{"abstract" if cls.is_abstract else ""} class {cls.name} {{',
            *add_indent(func_codes),
            '}'
        ]


class APIGenerator:
    def __init__(self, class_generator: ClassGenerator, classes: List[Class] = [], imports: List[str] = []) -> None:
        self.class_generator = class_generator
        self.classes = classes
        self.imports = imports

    def generate_to(self, file_path: str) -> str:
        code = []
        code.extend(self.imports)
        for cls in self.classes:
            code.append('')
            code.extend(self.class_generator.generate(cls))
        ret = '\n'.join(code)
        with open(file_path, 'w') as f:
            f.write(ret)
        return ret

say_hello = Function(name='say_hello', args=[Arg(name='name', type='str')], return_type='str', body=empty_body, is_abstract=True)
get_user_name = Function(name='get_user_name', args=[], return_type='str', body=empty_body, is_abstract=True)

AbstractAPI = Class('AbstractAPI', functions=[get_user_name, say_hello], is_abstract=True)

import os
py_api_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'src', 'api', 'abstract_api.py')
s = APIGenerator(class_generator=PyClassGenerator,
                 classes=[AbstractAPI], imports=[
                     'from abc import ABC, abstractmethod',
                 ]).generate_to(py_api_path)

ts_api_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'timelog_frontend', 'src', 'api', 'abstract_api.ts')
print('=' * 50, 'py\n', s, sep='')
s = APIGenerator(class_generator=TsClassGenerator,
                 classes=[AbstractAPI], imports=[]
                 ).generate_to(ts_api_path)
print('=' * 50, 'ts\n', s, sep='')
